package com.example.demo.controller;

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeCloseRequest;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayTradePagePayResponse;
import com.example.demo.config.AlipayConfig;
import com.example.demo.entity.OrderInfo;
import com.example.demo.entity.OrderRefund;
import com.example.demo.exception.CustomException;
import com.example.demo.service.impl.OrderInfoService;
import com.example.demo.service.impl.OrderRefundService;
import com.example.demo.utils.JsonUtils;
import com.example.demo.utils.RandomUtils;
import com.example.demo.utils.Result;
import com.example.demo.utils.ResultEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.support.CustomSQLExceptionTranslatorRegistrar;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Created by Hy on 2018/8/22.
 * 支付controller
 */
@Controller
public class PayController {
    private static Logger log = LoggerFactory.getLogger(PayController.class);
    @Autowired
    private OrderInfoService orderInfoService;
    @Autowired
    private OrderRefundService orderRefundService;
    @Autowired
    private AlipayConfig alipayConfig;
    @Autowired
    private AlipayClient alipayClient;

    @RequestMapping("/")
    public String showIndex(){
        return "index.html";
    }

    @RequestMapping("/alipay/payment")
    public void payment(String subject, String body, float money, HttpServletResponse response){
        //金额保留两位
        money = (float) (Math.round(money * 100)) / 100;

        //生成订单
        OrderInfo orderInfo = orderInfoService.createOrder(subject, body, money, alipayConfig.getSellerId());

        //1.设置请求参数
        AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
        //页面跳转同步通知页面路径
        alipayRequest.setReturnUrl(alipayConfig.getReturnUrl());
        //服务器异步通知页面路径
        alipayRequest.setNotifyUrl(alipayConfig.getNotifyUrl());

        //2.SDK已经封装掉了公共参数，这里只需要传入业务参数，请求参数
        Map<String,String> map = new HashMap<>(16);
        map.put("out_trade_no",orderInfo.getOrderId());
        map.put("total_amount",String.valueOf(money));
        map.put("subject",subject);
        map.put("body",body);
        //销售产品码
        map.put("product_code","FAST_INSTANT_TRADE_PAY");//目前仅支持该产品码
        alipayRequest.setBizContent(JsonUtils.objectToJson(map));
        response.setContentType("text/html;charset=utf-8");

        try {
            //3.生成支付表单
            AlipayTradePagePayResponse alipayResponse = alipayClient.pageExecute(alipayRequest);
            if(alipayResponse.isSuccess()){
                String result = alipayResponse.getBody();
                response.getWriter().write(result);
            }else{
                log.error("【支付表单生成】失败，错误信息：{}",alipayResponse.getSubMsg());
                response.getWriter().write("error");
            }
        } catch (Exception e) {
            log.error("【支付表单生成】异常，异常信息：{}",e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 该方式仅仅在买家付款完成以后自动跳转，因此只会进行一次
     * 支付宝服务器同步通知页面获取支付宝get过来反馈信息
     * 改方法执行完毕后跳转到成功页面即可
     * （1）该访问方式不是支付宝主动去调用商户页面，而是支付宝的程序利用页面自动跳转的函数，使用户的当前页面自动跳转；
     * （2）返回URL只有一分钟有效期，超过一分钟该链接地址会自动失效，验证会失败
     * （3）可在本机而不是只能在服务器上调试
     * @param request
     * @param response
     */
    @GetMapping("/alipay/return")
    public void alipayReturn(HttpServletRequest request,HttpServletResponse response){
        //获取参数
        Map<String,String> params = getPayParams(request);
        try {
            //验证订单
            boolean flag = orderInfoService.validOrder(params);
            if(flag){
                //验证成功后修改订单状态为已支付
                String orderId = params.get("out_trade_no");
                String tradeNo = params.get("trade_no");
                //更新状态
                orderInfoService.changeStatus(orderId,"TRADE_SUCCESS",tradeNo);

                response.setContentType("text/html;charset=utf-8");
                response.getWriter().write("<!DOCTYPE html>\n" +
                        "<html lang=\"en\">\n" +
                        "<head>\n" +
                        "    <meta charset=\"UTF-8\">\n" +
                        "    <title>支付成功</title>\n" +
                        "</head>\n" +
                        "<body>\n" +
                        "\n" +
                        "<div class=\"container\">\n" +
                        "    <div class=\"row\">\n" +
                        "        <p>订单号："+orderId+"</p>\n" +
                        "        <p>支付宝交易号："+tradeNo+"</p>\n" +
                        "        <a href=\"/\">返回首页</a>\n" +
                        "    </div>\n" +
                        "</div>\n" +
                        "\n" +
                        "</body>\n" +
                        "</html>");

            }else{
                log.error("【支付宝同步方法】验证失败");
                response.getWriter().write("支付验证失败");
            }
        } catch (Exception e) {
            log.error("【支付宝同步方法】异常，异常信息:{}",e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 服务异步通知，获取支付宝post过来反馈信息
     * 该方法无返回值，静默处理
     * 订单的状态以该方法为主，其他的状态修改方法为辅
     * （1）程序执行完后必须打印输出“success”
     * 如果商户反馈给支付宝的字符不是success这7个字符，支付宝服务器会不断重发通知，直到超过24小时22分钟。
     * （2）程序执行完成后，该页面不能执行页面跳转。
     * 如果执行页面跳转，支付宝会收不到success字符，会被支付宝服务器判定为该页面程序运行出现异常，而重发处理结果通知
     * （3）cookies、session等在此页面会失效，即无法获取这些数据
     * （4）该方式的调试与运行必须在服务器上，即互联网上访问
     * @param request
     * @param response
     */
    @PostMapping("/alipay/notify")
    public void alipayNotify(HttpServletRequest request,HttpServletResponse response){
        /**
         默认只有TRADE_SUCCESS会触发通知，如果需要开通其他通知，请联系客服申请
         触发条件名      触发条件描述          触发条件默认值
         TRADE_FINISHED    交易完成             false（不触发通知）
         TRADE_SUCCESS     支付成功             true（触发通知）
         WAIT_BUYER_PAY    交易创建             false（不触发通知）
         TRADE_CLOSED      交易关闭             false（不触发通知）
         */
        //获取参数
        Map<String,String> params = getPayParams(request);
        try {
            boolean flag = orderInfoService.validOrder(params);
            if(flag){
                //商户订单号
                String orderId = params.get("out_trade_no");
                //支付宝交易号
                String tradeNo = params.get("trade_no");
                //交易状态
                String tradeStatus = params.get("trade_status");

                switch(tradeStatus){
                    case "WAIT_BUYER_PAY":
                        orderInfoService.changeStatus(orderId,tradeStatus);
                        break;
                    /*
                      关闭订单
                      （1）订单已创建，但用户未付款，调用关闭交易接口
                      （2）付款成功后，订单金额已全部退款【如果没有全部退完，仍是TRADE_SUCCESS状态】
                     */
                    case "TRADE_CLOSED":
                        orderInfoService.changeStatus(orderId,tradeStatus);
                        break;
                    /*
                     *订单完成
                     * （1）退款日期超过可退款期限后
                     */
                    case "TRADE_FINISHED":
                        orderInfoService.changeStatus(orderId,tradeStatus);
                        break;
                    /*
                     *订单success
                     *  （1）用户付款成功
                     */
                    case "TRADE_SUCCESS":
                        orderInfoService.changeStatus(orderId,tradeStatus,tradeNo);
                        break;
                        default:break;
                }
                response.getWriter().write("success");
            }else{
                log.error("【支付异步方法】验证失败，错误信息:{}", AlipaySignature.getSignCheckContentV1(params));
                response.getWriter().write("fail");
            }
        } catch (Exception e) {
            log.error("【支付异步方法】异常，异常信息:{}",e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 交易查询
     * 以下两个参数任一即可
     * @param orderId 订单ID
     * @param alipayNo 支付宝交易号
     * @return
     */
    @PostMapping("/alipay/query")
    @ResponseBody
    public Result queryOrder(String orderId,String alipayNo){
        //1.设置请求参数
        AlipayTradeQueryRequest alipayRequest  = new AlipayTradeQueryRequest();
        Map<String,String> map = new HashMap<>(16);
        map.put("out_trade_no",orderId);
        map.put("trade_no",alipayNo);
        alipayRequest.setBizContent(JsonUtils.objectToJson(map));

        try {
            //2.请求
            String json = alipayClient.execute(alipayRequest).getBody();
            Map<String,Object> resMap = JsonUtils.jsonToPojo(json, Map.class);
            Map<String,String> responseMap = (Map) resMap.get("alipay_trade_query_response");

            //获得返回状态码
            String code = responseMap.get("code");
            if ("10000".equals(code)){
                //获取查询结果
                String outTradeNo = responseMap.get("out_trade_no");
                OrderInfo orderInfo = orderInfoService.getByIdOrAlipayNo(orderId, null);
                Map<String,Object> result = new HashMap<>(16);
                result.put("orderInfo",orderInfo);
                result.put("buyer_logon_id",responseMap.get("buyer_logon_id"));
                result.put("trade_status",responseMap.get("trade_status"));

                return Result.ok(null,result);
            }else{
                log.error("【支付宝查询】错误，错误码:{},错误信息:{}",code,responseMap.get("sub_msg"));
                return Result.error(ResultEnum.ALIPAY_QUERY_ERROR);
            }
        } catch (Exception e) {
            log.error("【支付宝查询】异常,异常信息:{}",e.getMessage());
            e.printStackTrace();
        }
        return Result.error(ResultEnum.ALIPAY_QUERY_ERROR);
    }

    /**
     * 退款
     * 订单ID、支付宝交易号任选一即可
     * @param orderId 订单ID
     * @param alipayNo 支付宝交易号
     * @param money 退款金额，注意不要大于支付金额
     * @param reason 退款原因
     * @return
     */
    @PostMapping("/alipay/refund")
    @ResponseBody
    public Result refund(String orderId,String alipayNo,float money,String reason){
        if(money==0){
            throw new CustomException(ResultEnum.MONEY_ERROR);
        }
        //金额保留两位
         money = (float)(Math.round(money * 100))/100;

        OrderInfo orderInfo = orderInfoService.getByIdOrAlipayNo(orderId, alipayNo);
        if (orderInfo == null){
            throw new CustomException(ResultEnum.ORDER_NOT_EXIST);
        }

        //只有订单状态为TRADE_SUCCESS,才能退款
        if(!"TRADE_SUCCESS".equals(orderInfo.getStatus())){
            throw new CustomException(ResultEnum.ORDER_STATUS_NOT_SUPPORT);
        }

        //判断金额是否足够退款
        float totalMoney = orderInfo.getMoney();
        float refundMoney = orderInfo.getRefundMoney();
        float canRefundMoney = (float)(Math.round((totalMoney - refundMoney) * 100)) / 100;
        if(canRefundMoney < money){
            throw new CustomException(ResultEnum.PARAMS_ERROR.getCode(),
                    "退款金额超过可退款金额，最大退款金额："+canRefundMoney);
        }

        //生成退款请求号，标识一次退款请求，同一笔交易多次退款需要保证唯一，如需部分退款，则此参数必传
        String refundId = RandomUtils.time();

        //1.设置请求参数
        AlipayTradeRefundRequest alipayRequest = new AlipayTradeRefundRequest();
        Map<String,String> map = new HashMap<>(16);
        map.put("out_trade_no",orderId);
        map.put("trade_no",alipayNo);
        map.put("refund_amount",String.valueOf(money));
        map.put("refund_reason",reason);
        map.put("out_request_no",refundId);

        try {
            //2.请求
            String json = alipayClient.execute(alipayRequest).getBody();

            Map<String,Object> resMap = JsonUtils.jsonToPojo(json, Map.class);
            Map<String,String> responseMap = (Map) resMap.get("alipay_trade_refund_response");
            
            //获得返回状态码
            String code = responseMap.get("code");
            if("10000".equals(code)){
                //插入数据库
                orderRefundService.createRefund(refundId,reason,money,responseMap);
                return Result.ok();
            }else{
                String subMsg = responseMap.get("sub_msg");
                log.error("【支付宝退款】错误，错误码:{},错误信息：{}",code,subMsg);

                //如果错误信息为交易状态不合法，说明本地状态与服务器的不一致，需要手动同步
                if("交易状态不合法".equals(subMsg)){
                    orderInfoService.syncStatus(orderId,alipayNo);
                }

                return Result.error(ResultEnum.ALIPAY_QUERY_ERROR);
            }
        } catch (Exception e) {
            log.error("【支付宝退款】异常，异常原因：{}",e.getMessage());
            e.printStackTrace();
        }
        return Result.error(ResultEnum.ALIPAY_REFUND_ERROR);
    }

    /**
     * 退款记录查询
     * orderId和alipayNo二选一即可
     * refundId可选，如果不填，则查询该订单的所有退款
     * @param orderId  订单ID
     * @param alipayNo 支付宝交易号
     * @param refundId 退款请求号
     * @return
     */
    @PostMapping("/alipay/refund/query")
    @ResponseBody
    public Result refundQuery(String orderId,String alipayNo,String refundId){
        OrderInfo orderInfo = orderInfoService.getByIdOrAlipayNo(orderId, alipayNo);
        if(orderInfo  == null){
            throw new CustomException(ResultEnum.ORDER_NOT_EXIST);
        }

        //这里我直接查询数据库了，也可以请求支付宝
        List<OrderRefund> refunds = orderRefundService.listRefund(orderInfo.getOrderId(), refundId);
        return Result.ok(null,refunds);
    }

    /**
     * 交易关闭
     * orderId和alipayNo二选一即可
     * @param orderId
     * @param alipayNo
     * @return
     */
    @PostMapping("/alipay/close")
    @ResponseBody
    private Result closeOrder(String orderId,String alipayNo){
        //设置请求参数
        AlipayTradeCloseRequest alipayRequest = new AlipayTradeCloseRequest();
        Map<String,String> map = new HashMap<>(16);
        map.put("out_trade_no",orderId);
        map.put("trade_no",alipayNo);
        alipayRequest.setBizContent(JsonUtils.objectToJson(map));


        try {
            //请求
            String json = alipayClient.execute(alipayRequest).getBody();

            Map<String,Object> resMap = JsonUtils.jsonToPojo(json, Map.class);
            Map<String,String> responseMap = (Map) resMap.get("alipay_trade_close_response");

            String code = responseMap.get("code");
            if("10000".equals(code)){
                //将状态更改为关闭
                String outTradeNo = responseMap.get("out_trade_no");
                orderInfoService.changeStatus(outTradeNo,"TRADE_CLOSED");
                return Result.ok();
            }else{
                log.error("【支付宝交易关闭】失败，错误原因：{}",responseMap.get("sub_msg"));
                return Result.error(ResultEnum.ALIPAY_CLOSE_ERROR);
            }
        } catch (AlipayApiException e) {
            log.error("【支付宝交易关闭】异常，异常原因：{}",e.getMessage());
            e.printStackTrace();
        }
        return Result.error(ResultEnum.ALIPAY_CLOSE_ERROR);
    }
    private Map<String,String> getPayParams(HttpServletRequest request) {
        Map<String,String> params = new HashMap<>(16);
        Map<String, String[]> requestParams = request.getParameterMap();
        Iterator<String> iter = requestParams.keySet().iterator();
        while(iter.hasNext()){
            String name = iter.next();
            String[] values = requestParams.get(name);
            String valueStr = "";
            for (int i =0;i<values.length;i++){
                valueStr = (i==values.length -1) ? valueStr+values[i] : valueStr+values[i]+",";
            }
            //乱码解决，这段代码在出现乱码时使用
//            valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
            params.put(name,valueStr);
        }
        return params;
    }
}
